%{
#include <fcntl.h>

#include <cstring>
#include <unistd.h>
#include <stdio.h>
#include "creator.h"

#include <getopt.h>

// #define YYDEBUG 1

using std::cout;
using std::cerr;
using std::endl;

u64 DEFAULT_FLAGS = 0;
TestCreator testCreator;


extern  int yyparse(void);
extern  int yylex(void);
extern FILE* yyin;
extern  int yylineno;
extern  char* yytext;
extern int yydebug;

void yyerror(const char *s)
{
  cerr << endl << "Bison error: " << s << endl;
  cerr << "  at line: " << yylineno << endl;
  if (yytext)
    cerr << "  text: " << yytext << endl;
}

#define ERROR_ON(cond, str) if (cond) { yyerror(str); YYABORT; }

interrupt_info makeResult(bool is_int, int int_num, bool is_errcode, int errcode)
{
  interrupt_info res;
  res.int_num = int_num;
  res.interrupt = is_int;
  res.has_error_code = is_errcode;
  res.unused0 = 0;
  res.unused1 = 0;
  res.error_code = errcode;

  return res;
}


bool checkFileName(char *fileName, bool print = true)
{
  int fd = open(fileName, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
  
  if (fd == -1) {
    if (print)
      cout << "can't open file '" << fileName << "': " << strerror(errno) << endl;
    
    return false;
  }

  close(fd);
  return true;
}


int main(int argc, char *argv[])
{
/*   yydebug = 1; */
  char usage[] = "Usage: tc -o outTestFile [-r outResultFile] inputFile";
  char *resultFile = 0, *testFile = 0;
  
  int opt;
  while ((opt = getopt(argc, argv, "o:r:")) != -1) {
    switch (opt) {
      case 'o':
	testFile = optarg;
	break;
      case 'r':
	resultFile = optarg;
	break;
      default:
	cout << usage << endl;
	return 1;
    }
  }
  
  if (optind != argc-1 || testFile == 0) {
      cout << usage << endl;
      return 1;
  }
  
  yyin = fopen(argv[argc-1], "r");
  
  if ( yyin == 0 || !checkFileName(testFile) ||
      (resultFile && !checkFileName(resultFile)) ) {
      return 2;
  }

  if (yyparse() == 1) {
    // error message was already printed from yyerror
    return 1;
  }

  testCreator.saveTest(testFile);

  if (resultFile)
    testCreator.saveResult(resultFile);

  return 0;
}
%}
/* %debug */
%union {
  Flags flags;
  u64 flag;
  Address *address;
  u32 offset;
  u32 number;
  char *cstr;
  interrupt_info result;
  test_command cmd;
};

%token <number> NUMBER OPTION CMD_ID PAGING_MODE
%token <flag>   FLAG
%token <cstr>   NAME
%token <offset> SIZE
%token PAGE_FAULT INTERRUPT OK ENDL CMD_RES

%type <flag> flag
%type <number> page page_unchecked path
%type <flags> flags flags_not_empty
%type <address> address address_left
%type <cmd> command_normal parameters
%type <result> result

%%

test: options commands
  | options commands command //no new line at the eof
  ;
  
options: 
  | options option ENDL
  ;
  
option: OPTION PAGING_MODE
  {
    testCreator.setOption($1, $2);
  }
  | OPTION flags
  {
    ///if (
    testCreator.setOption($1, $2.flags);
  }
  ;
  
commands: 
  | commands command ENDL
  ;
  
command: path
  | command_normal
  {
    $1.has_result = 0;
    testCreator.addCommand($1);
  }
  | command_normal '|' result
  {
    $1.has_result = 1;
    testCreator.addResult($3);
    testCreator.addCommand($1);
  }
  ;
  
command_normal: CMD_ID parameters
  {
    $$ = $2;
    $$.cmd = $1;
    $$.has_result = 0;
  }
  ;
  
parameters: address
  {
    $$.vaddr = $1->to64();
    $$.page = $1->getPage();
  }
  | page '.' NUMBER page
  {
    $$.page = $1;
    $$.pos = $3;
    $$.page2 = $4;
  }
  | page '.' NUMBER flags
  {
    $$.page = $1;
    $$.pos = $3;
    $$.flags = $4.flags;
    $$.uflags = $4.uflags;
  }
  ;
  
path: page
  | path '-' NUMBER ',' flags '>' page
  {
    ERROR_ON ($5.uflags != 0, "dont use '!flag' construction in map paths");
    testCreator.addMap($1, $3, $7, $5);
    $$ = $7;
  }
  | path '-' NUMBER '>' page
  {
    Flags fl = {0, 0};
    testCreator.addMap($1, $3, $5, fl);
    $$ = $5;
  }
  ;

page: page_unchecked
  {
    ERROR_ON (PAGE_UNDEFINED == $$, "cant get page. Dont you try setting page offset at not first occurrence?");
  }
  ;

page_unchecked: '*'
  {
    $$ = testCreator.newUnnamedPage();
  }
  | NAME
  {
    $$ = testCreator.getPage($1);
  }
  | '*' SIZE
  {
    $$ = testCreator.newUnnamedPage($2);
  }
  | NAME SIZE
  {
    cerr << $1 << endl;
    $$ = testCreator.newPage($1, $2);
  }
  ;

flags:
  {
    $$.flags = 0;
    $$.uflags = 0;
  }
  | flags_not_empty
  {
    ERROR_ON (($1.flags & $1.uflags) != 0, "dont use 'some_flag' and '!the_same_flag' in one expression");
  }
  ;
  
flags_not_empty: flag
  {
    $$.flags = $1;
    $$.uflags = 0;
  }
  | '!' flag
  {
    $$.flags = 0;
    $$.uflags = $2;
  }
  | flags_not_empty flag
  {
    $$.flags |= $2;
  }
  | flags_not_empty '!' flag
  {
    $$.uflags |= $3;
  }
  ;
  
flag: FLAG
  | NUMBER
  {
    $$ = 1ull << $1
  }
  ;
  
result: OK
  {
    $$ = makeResult(false, 0, false, 0);
  }
  | INTERRUPT NUMBER
  {
    $$ = makeResult(true, $2, false, 0);
  }
  | PAGE_FAULT '(' flags ')'
  {
    ERROR_ON ($3.uflags != 0, "dont use '!flag' construction in #PF flags");
    $$ = makeResult(true, 14, true, $3.flags);
  }
  ;
  
address: address_left
  | address_left '(' page ')'
  {
    $$->setPage($3);
  }
  ;
    
address_left: NUMBER
  {
    $$ = new Address($1);
  }
  | address '.' NUMBER
  {
    $$->push($3);
  }
  ;
  
%%
